package com.hwtx.form.domain.handle.datamodel;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hwtx.form.annotation.FormType;
import com.hwtx.form.domain.def.FormDef;
import com.hwtx.form.domain.ds.DataSourceService;
import com.hwtx.form.domain.handle.FormDataHandle;
import com.hwtx.form.domain.listener.DataModelChangeEvent;
import com.hwtx.form.domain.listener.EventPublisher;
import com.hwtx.form.domain.repo.DataModelRepo;
import com.hwtx.form.domain.repo.FormConfigRepo;
import com.hwtx.form.domain.repo.FormRepo;
import com.hwtx.form.domain.vo.*;
import com.hwtx.form.dto.FormListQuery;
import com.hwtx.form.persistence.DataSourceRepo;
import com.hwtx.form.persistence.SQLType;
import com.hwtx.form.persistence.entity.FormDefDetail;
import com.hwtx.form.query.FormValueQuery;
import com.hwtx.form.util.JacksonUtil;
import io.geekidea.boot.framework.exception.BusinessException;
import io.geekidea.boot.framework.page.Paging;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.DATA_MODEL_HANDLE_NAME;
import static com.hwtx.form.domain.FormConstants.Status;
import static com.hwtx.form.domain.handle.datamodel.DataModel.*;
import static com.hwtx.form.util.FormUtil.getStrPostValue;
import static com.hwtx.form.util.JacksonUtil.map2Oject;

@FormType(value = DATA_MODEL_HANDLE_NAME, actionClass = DataModelAction.class, comment = "数据模型管理")
@Slf4j
public class DataModelHandle implements FormDataHandle {

    @Resource
    EventPublisher eventPublisher;
    @Resource
    private DataSourceRepo dataSourceRepo;
    @Resource
    private DataModelRepo dataModelRepo;
    @Resource
    private FormConfigRepo formConfigRepo;
    @Resource
    private FormRepo formRepo;
    @Resource
    private DbSynService dbSynService;

    @Override
    public FormRepData getFormData(FormDef formDef, FormValueQuery formValueQuery) {
        Long queryId = formValueQuery.getValueIds().getFirst();
        String type = getSearchValue(formValueQuery.getParameters(), "tp");
        if (Objects.equals("details", type)) {
            return FormRepData.builder().data(dataModelRepo.getDetail(queryId)).build();
        } else if (Objects.equals("relate", type)) {
            return FormRepData.builder().data(dataModelRepo.getRelate(queryId)).build();
        }
        DataModel dataModel = dataModelRepo.getDataModel(queryId);
        Map<String, Object> data = JacksonUtil.object2Map(dataModel);
        data.put("hasDetail", dataModelRepo.countDetails(dataModel.getModelName(), Status.VALID) > 0 ? 1 : 0);
        return FormRepData.builder().data(data).build();
    }

    @Override
    @Transactional
    public Object saveFormData(FormDef formDef, FormData formData) {
        String type = (String) formData.getData().get("tp");
        if (Objects.equals("details", type)) {
            DataModelDetail dataModelDetail = map2Oject(formData.getData(), DataModelDetail.class);
            boolean ret = saveDetails(dataModelDetail);
            eventPublisher.publishEvent(new DataModelChangeEvent(this, dataModelDetail.getModelName()));
            return ret;

        } else if (Objects.equals("relate", type)) {
            return DataSourceService.invoke(formData.getData().get("ds"), () -> saveRelate(formData));
        }
        return saveModel(map2Oject(formData.getData(), DataModel.class));
    }

    private boolean saveDetails(DataModelDetail dataModelDetail) {
        if (dataModelDetail == null) {
            throw new BusinessException("数据模型详情不能为空");
        }
        SQLType sqlType = dataModelDetail.getType();
        if (sqlType != null) {
            if (sqlType.getPrecision() != null && sqlType.getPrecision() < 0) {
                dataModelDetail.setLength(null);
            }
        }
        if (dataModelDetail.getNum() != null) {
            List<DataModelDetail> details = dataModelRepo.getDetails(DataModelDetailQuery.builder()
                    .modelName(dataModelDetail.getModelName()).status(Status.VALID).num(dataModelDetail.getNum()).build());
            if (!details.isEmpty()) {
                if (!(details.size() == 1 && Objects.equals(details.getFirst().getId(), dataModelDetail.getId()))) {
                    throw new BusinessException("编号不能重复");
                }
            }
        }
        if (dataModelDetail.getId() == null) {
            return dataModelRepo.saveDetail(dataModelDetail) > 0;
        }
        return dataModelRepo.updateDataModelDetailById(dataModelDetail) > 0;
    }

    private boolean saveRelate(FormData formData) {
        DataModelRelate dataModelRelate = map2Oject(formData.getData(), DataModelRelate.class);
        if (dataModelRelate == null) {
            throw new BusinessException("数据模型关联不能为空");
        }
        if (Objects.equals(dataModelRelate.getFirstName(), dataModelRelate.getSecondName())) {
            throw new BusinessException("关联表不能相同");
        }
        if (StringUtils.isEmpty(dataModelRelate.getJoinType())) {
            throw new BusinessException("关联类型不能为空");
        }
        List<DataModelRelate> relates = dataModelRepo.getRelates(dataModelRelate.getModelName());
        relates.forEach(relate -> {
            if (Objects.equals(relate.getSecondName(), dataModelRelate.getSecondName()) &&
                    Objects.equals(relate.getFirstName(), dataModelRelate.getFirstName()) ||
                    Objects.equals(relate.getFirstName(), dataModelRelate.getSecondName())
                            && Objects.equals(relate.getSecondName(), dataModelRelate.getFirstName())) {
                throw new BusinessException("关联关系表已存在");
            }
        });
        if (dataModelRelate.getId() == null) {
            dataModelRelate.setCreateTime(new Date());
            boolean ret = dataModelRepo.saveRelate(dataModelRelate) > 0;
            if (ret) {
                List<DataModelDetail> existDetails = dataModelRepo.getDetails(dataModelRelate.getModelName(), Status.VALID);
                if (existDetails.isEmpty()) {
                    AtomicInteger num = new AtomicInteger(1);
                    synDataModelDetail(dataModelRelate.getFirstName(), dataModelRelate.getModelName(), num, column -> true);
                    synDataModelDetail(dataModelRelate.getSecondName(), dataModelRelate.getModelName(), num, column -> true);
                } else {
                    List<String> existTables = existDetails.stream().map(DataModelDetail::getTableName).distinct().toList();
                    AtomicInteger maxNum = new AtomicInteger(existDetails.stream().map(DataModelDetail::getNum).max(Integer::compareTo).orElse(0));
                    maxNum.incrementAndGet();
                    if (!existTables.contains(dataModelRelate.getFirstName())) {
                        synDataModelDetail(dataModelRelate.getFirstName(), dataModelRelate.getModelName(), maxNum, column -> true);
                    }
                    if (!existTables.contains(dataModelRelate.getSecondName())) {
                        synDataModelDetail(dataModelRelate.getSecondName(), dataModelRelate.getModelName(), maxNum, column -> true);
                    }
                }
            }
            return ret;
        }
        return dataModelRepo.updateRelate(dataModelRelate) > 0;
    }

    private Object saveModel(DataModel dataModel) {
        if (dataModel == null) {
            throw new BusinessException("数据模型不能为空");
        }
        if (dataModel.getMaster() != null && dataModel.getMaster().contains("-")) {
            throw new BusinessException("主表不可包含-");
        }
        if (StringUtils.isNotEmpty(dataModel.getSqlContent()) && dataModel.getSqlContent().contains("${")
                && dataModel.getSqlContent().contains("}")) {
            throw new BusinessException("sql参数不能使用$｛｝,请使用@");
        }
        dataModel.setUpdateTime(new Date());
        if (dataModel.getId() == null) {
            dataModel.setCreateTime(new Date());
            DataModel dataModelByName = dataModelRepo.getDataModel(dataModel.getName());
            if (dataModelByName != null) {
                throw new BusinessException("数据模型名称不能重复");
            }
            dataModelRepo.saveDataModel(dataModel);
            dbSynService.sync(dataModel.getName(), false);
            return ResultVo.builder().success(true).data(Map.of("id", dataModel.getId())).build();
        }
        DataModel oldDataModel = dataModelRepo.getDataModel(dataModel.getId());
        if (!Objects.equals(dataModel.getName(), oldDataModel.getName())) {
            dataModelRepo.updateDetailModelName(dataModel.getName(), oldDataModel.getName());
            dataModelRepo.updateRelateModelName(dataModel.getName(), oldDataModel.getName());
        }
        if (!Objects.equals(dataModel.getSqlContent(), oldDataModel.getSqlContent())) {
            if (CollectionUtils.isNotEmpty(formConfigRepo.getFormDetailByModelName(oldDataModel.getName()))) {
                throw new BusinessException("该数据模型已被表单引用，不能修改sql");
            }
            dataModelRepo.deleteDetails(oldDataModel.getName(), null, Status.VALID);
            dataModelRepo.saveDetails(parserAndBuildDetails(DataModelBuildInfo.builder()
                    .sql(dataModel.getSqlContent()).modelName(dataModel.getName()).build(), dataSourceRepo));
        }
        if ((StringUtils.isNotBlank(dataModel.getMaster()) && StringUtils.isNotBlank(oldDataModel.getMaster())
                && !Objects.equals(dataModel.getMaster(), oldDataModel.getMaster())) ||
                (StringUtils.isNotBlank(dataModel.getDs()) && StringUtils.isNotBlank(oldDataModel.getDs())
                        && !Objects.equals(dataModel.getDs(), oldDataModel.getDs()))) {
            List<FormDefDetail> formDefDetails = formConfigRepo.getFormDetailByModelName(oldDataModel.getName());
            if (CollectionUtils.isNotEmpty(formDefDetails)) {
                String forms = formDefDetails.stream().map(formDefDetail -> formRepo.getFormNameById(formDefDetail.getFormId())).collect(Collectors.joining(","));
                throw new BusinessException("该数据模型已被表单【" + forms + "】引用，不能修改主表");
            }
            dataModelRepo.deleteDetails(oldDataModel.getName(), null, Status.VALID);
            if (dataSourceRepo.exist(dataModel.getMaster())) {
                AtomicInteger num = new AtomicInteger(1);
                synDataModelDetail(dataModel.getMaster(), dataModel.getModelName(), num, column -> true);
            }
            if (Objects.equals(dataModel.getTableType(), TABLE_TYPE_AGGREGATE)) {
                dataModelRepo.deleteRelate(dataModel.getModelName());
            }
        }
        return dataModelRepo.updateDataModel(dataModel) > 0;
    }

    @Override
    @Transactional
    public Object removeFormData(FormDef formDef, FormValueQuery formValueQuery) {
        if (CollectionUtils.isEmpty(formValueQuery.getValueIds())) {
            return false;
        }
        String type = getSearchValue(formValueQuery.getParameters(), "tp");
        if (Objects.equals("details", type)) {
            return dataModelRepo.deleteDetails(formValueQuery.getValueIds().getFirst());
        } else if (Objects.equals("relate", type)) {
            DataModelRelate relate = dataModelRepo.getRelate(formValueQuery.getValueIds().getFirst());
            boolean ret = dataModelRepo.deleteRelate(formValueQuery.getValueIds().getFirst());
            if (ret) {
                List<DataModelRelate> relates = dataModelRepo.getRelates(relate.getModelName());
                List<String> tables = Lists.newArrayList();
                relates.stream().filter(dataModelRelate -> !dataModelRelate.getId().equals(relate.getId())).forEach(dataModelRelate -> {
                    tables.add(dataModelRelate.getFirstName());
                    tables.add(dataModelRelate.getSecondName());
                });
                if (!tables.contains(relate.getFirstName())) {
                    dataModelRepo.deleteDetails(relate.getModelName(), relate.getFirstName(), Status.VALID);
                }
                if (!tables.contains(relate.getSecondName())) {
                    dataModelRepo.deleteDetails(relate.getModelName(), relate.getSecondName(), Status.VALID);
                }
            }
            return ret;
        }
        DataModel dataModel = dataModelRepo.getDataModel(formValueQuery.getValueIds().getFirst());
        List<FormDefDetail> formDefDetails = formConfigRepo.getFormDetailByModelName(dataModel.getName());
        if (CollectionUtils.isNotEmpty(formDefDetails)) {
            throw new BusinessException("数据模型已关联表单，不能删除,表单 = " + formDefDetails.getFirst().getFormId());
        }
        dataModelRepo.deleteDataModel(formValueQuery.getValueIds().getFirst());
        dataModelRepo.deleteDetails(dataModel.getModelName(), null, null);
        if (DataModel.isAggTableModel(dataModel.getTableType())) {
            dataModelRepo.deleteRelate(dataModel.getModelName());
        }
        return true;
    }

    @Override
    public Paging<DataModelDetail> page(FormDef formDef, FormListQuery formListQuery) {
        String modelName = getStrPostValue(formListQuery.getSearchPostData(), "modelName");
        String master = getStrPostValue(formListQuery.getSearchPostData(), "master");
        String ds = getStrPostValue(formListQuery.getSearchPostData(), "ds");
        if (StringUtils.isBlank(master)) {
            return new Paging<>(Collections.emptyList(), 0, formListQuery.getPageIndex(), formListQuery.getPageSize());
        }
        DataModelDetailsDto dataModelDetailsDto = buildDataModelDetail(DataModelBuildInfo.builder().master(master).ds(ds)
                .modelName(modelName).page(true).pageIndex(formListQuery.getPageIndex()).pageSize(formListQuery.getPageSize()).build());
        Paging<DataModelDetail> ret = dataModelDetailsDto.getPageData();
        Map<String, Object> uData = Maps.newHashMap();
        uData.put("add", dataModelDetailsDto.isAdd());
        uData.put("syn", dataModelDetailsDto.isSyn());
        uData.put("init", dataModelDetailsDto.isInit());
        uData.put("canOp", dataModelDetailsDto.isCanOp());
        ret.setUData(uData);
        return ret;
    }

    @Override
    public FormListVo<?> query(FormDef formDef, FormListQuery formListQuery) {
        String type = getSearchValue(formListQuery.getSearchData(), "type");
        if (StringUtils.isBlank(type)) {
            FormListVo<DataModel> formListVo = new FormListVo<>();
            List<DataModel> models = dataModelRepo.getDataModels(DataModelQuery.builder().modelName(getSearchValue(formListQuery.getSearchData(), "name")).build());
            formListVo.setRows(models);
            return formListVo;
        }
        String modelName = getSearchValue(formListQuery.getSearchData(), "modelName");
        String master = getSearchValue(formListQuery.getSearchData(), "master");
        String ds = getSearchValue(formListQuery.getSearchData(), "ds");
        switch (type) {
            case "relateDetails" -> {
                FormListVo<DataModelDetail> formListVo = new FormListVo<>();
                formListVo.setRows(DataModelRelate.buildDetails(DataModelBuildInfo.builder().modelName(modelName)
                        .sql(getSearchValue(formListQuery.getSearchData(), "sql"))
                        .master(master).ds(ds)
                        .build(), dataSourceRepo));
                return formListVo;
            }
            case "relate" -> {
                FormListVo<DataModelRelateVo> formListVo = new FormListVo<>();
                formListVo.setRows(DataModelRelate.listRelate(DataModelBuildInfo.builder().modelName(modelName).ds(ds).master(master).build()));
                formListVo.setCount(formListVo.getRows().size());
                formListVo.setData("draft");
                return formListVo;
            }
        }
        return new FormListVo<>();
    }

    private void synDataModelDetail(String table, String modelName, AtomicInteger num, Predicate<String> filter) {
        List<String> uniqueColumns = dataSourceRepo.getConstraintColumns(table);
        dataSourceRepo.getColumns(table).entrySet().stream().filter(entry -> filter.test(entry.getKey()))
                .forEach(entry ->
                        dataModelRepo.saveDetail(buildDataModelDetailByColumn(table, entry.getKey(), entry.getValue(), num, modelName, uniqueColumns)));
    }

    private String getSearchValue(Map<String, String[]> data, String name) {
        String[] value = data.get(name);
        if (ArrayUtils.isNotEmpty(value)) {
            return StringUtils.isBlank(value[0]) ? null : value[0];
        }
        return null;
    }
}
